﻿using System;
using System.Collections.Generic;
using System.Linq;

using System.Net.Http.Headers;
using Sustainalytics.ESGComputing.Service.Tests.GovernanceUnitTests.Fixtures;
using Sustainalytics.ESGRatings.DataContract.Governance;
using Xunit;
using Xunit.Abstractions;

namespace Sustainalytics.ESGComputing.Service.Tests.GovernanceUnitTests
{
    public class GovernanceScoreTests : Disposable
    {
        private readonly ITestOutputHelper _output;
        private readonly BaseGovernanceFixture _dataFixture;
        private readonly GovernanceHttpClient _sutClient;
        private const float Tolerance = 1e-4f;

        public GovernanceScoreTests(BaseGovernanceFixture dataFixture, ITestOutputHelper output)
        {
            _dataFixture = dataFixture;
            _output = output;
            _sutClient = new GovernanceHttpClient();
            _sutClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        }


        private class FloatComparer : IEqualityComparer<float>
        {
            public bool Equals(float x, float y)
            {
                return Math.Abs(x - y) <Tolerance;
            }

            public int GetHashCode(float obj)
            {
                var hashCode =  obj.GetHashCode();
                return hashCode;
            }
        }

        private class IssueComparer : IEqualityComparer<Tuple<Guid, String, float>>
        {

            public bool Equals(Tuple<Guid, string, float> x, Tuple<Guid, string, float> y)
            {
                return x.Item1 == y.Item1 && x.Item2 == y.Item2 && (Math.Abs(x.Item3 - y.Item3) <Tolerance);
            }

            public int GetHashCode(Tuple<Guid, string, float> obj)
            {
                unchecked
                {
                    return (obj.Item1.GetHashCode()*397)
                           ^ (obj.Item2.GetHashCode()*397)
                           ^ (obj.Item3.GetHashCode());


                }
            }
        }

        private class CompanyScoreComparer : IEqualityComparer<Tuple<Guid, float>>
        {

            public bool Equals(Tuple<Guid,  float> x, Tuple<Guid,  float> y)
            {
                return x.Item1.Equals( y.Item1)  && (Math.Abs(x.Item2 - y.Item2) < Tolerance);
            }

            public int GetHashCode(Tuple<Guid,  float> obj)
            {
               
                    unchecked
                    {
                        return (obj.Item1.GetHashCode() * 397) ^ obj.Item2.GetHashCode();
                    }
                
            }
        }

        private GovernanceRatingsDto CallComputation()
        {
            GovernanceRatingsDto computeResult = _sutClient.GetComputationResult(_dataFixture.SetId, _dataFixture.MatrixId);

            _output.WriteLine(computeResult.Dump(20));

            return computeResult;
        }

        private IEnumerable<Tuple<Guid, string, float>> GetIssueWeightsByRegionAndIssueCode( GovernanceRatingsDto result,string issue)
        {
            return result.CompanyRating
                .SelectMany(x => x.Scores.Where(i => string.Equals(i.Issue, issue, StringComparison.InvariantCultureIgnoreCase)), (company, scores) =>
                    new Tuple<Guid, string, float>(company.CompanyId, scores.Issue, scores.Weight));
        }

        private IEnumerable<Tuple<Guid, string, float>> GetIssueWeightedScoresByRegionAndIssueCode(GovernanceRatingsDto result, string issue)
        {
            return result.CompanyRating
                .SelectMany(x => x.Scores.Where(i => string.Equals(i.Issue, issue, StringComparison.InvariantCultureIgnoreCase)), (company, scores) =>
                    new Tuple<Guid, string, float>(company.CompanyId, scores.Issue, scores.WeightedScore));
        }

        private IEnumerable<Tuple<Guid, string, float>> GetIssueScoresByRegionAndIssueCode(GovernanceRatingsDto result, string issue)
        {
            return result.CompanyRating
                .SelectMany(x => x.Scores.Where(i => string.Equals(i.Issue, issue, StringComparison.InvariantCultureIgnoreCase)), (company, scores) =>
                    new Tuple<Guid, string, float>(company.CompanyId, scores.Issue, scores.Score));
        }
        
        private IEnumerable<Tuple<Guid, string, float>> GetIssueWeightsByRegionAndIssueIndex( GovernanceRatingsDto result,int issueIndex)
        {
            return result.CompanyRating
                .Select(x =>
                    new Tuple<Guid, string, float>(x.CompanyId, x.Scores.ElementAt(issueIndex).Issue, x.Scores.ElementAt(issueIndex).Weight));
        }


     
        public class GivenOneCompany : GovernanceScoreTests, IClassFixture<GovernanceMultipleCompaniesComputingFixture>
        {

            public GivenOneCompany(GovernanceMultipleCompaniesComputingFixture dataFixture, ITestOutputHelper output)
                : base(dataFixture, output)
            {
            }

            [Theory
            ,InlineData("EUR","G.1")
            ,InlineData("EUR","G.2")]
            public void Issue_Weights_Are_Returned_as_in_RegionMatrix(string region,string issue)
            {
               
                    var result = CallComputation();
                    var companyIdsInRegion = _dataFixture.GetCompaniesInRegion(region);
                    var expected = _dataFixture.GetIssuesWeights(region, issue);

                    var actual = GetIssueWeightsByRegionAndIssueCode(result, issue) .Where(x => companyIdsInRegion.Contains(x.Item1));

                    _output.WriteLine(actual.Dump());
                    Assert.Equal(expected, actual);
                
            }

           [Fact]
            public void Issue_Weight_for_GO_Equals_One()
            {
                var result = CallComputation();

                var actual = GetIssueWeightsByRegionAndIssueIndex(result, 0);
                _output.WriteLine(actual.Dump());

               var differences = actual.Select(x => Math.Abs(x.Item3 - 1));
                _output.WriteLine(differences.Dump());

                Assert.True(differences.All(x => x <Tolerance), "All G0 weights should equal 1");
                

            }

            [Theory
            , InlineData("EUR","G.1")
            , InlineData("EUR","G.2")
            ]
            public void Issue_Weighted_Scores_Equal_Score_x_IssueWeight(string region,string issue)
            {
                var result = CallComputation();

                var weights = GetIssueWeightsByRegionAndIssueCode(result, issue);
                var scores = GetIssueScoresByRegionAndIssueCode(result, issue);

                var expected = from score in scores
                    join weight in weights on new {score.Item1, score.Item2} equals new {weight.Item1, weight.Item2}
                    select new Tuple<Guid, string, float>(score.Item1, score.Item2, score.Item3*weight.Item3);
                
                var actual = GetIssueWeightedScoresByRegionAndIssueCode(result, issue);

                _output.WriteLine(expected.Dump());
                Assert.Equal(expected, actual);
            }


            [Theory
            ,InlineData("EUR")]
            public void Issue_Scores_Are_Computed_As_Specified(string region)
            {
              var result = CallComputation();
              var companyGuid = _dataFixture.GetCompaniesInRegion(region).FirstOrDefault();
                var expected = _dataFixture.ComputeIssuesScore(companyGuid);

              var actual = result.CompanyRating.FirstOrDefault(x => x.CompanyId == companyGuid)
                  .Scores.Skip(1)
                  .Select(s => new Tuple<Guid, string, float>(companyGuid, s.Issue, s.Score));

                _output.WriteLine("-------expected--------------");
              _output.WriteLine(expected.Dump());
              _output.WriteLine("-------from CUDA--------------");
                _output.WriteLine(actual.Dump());

              Assert.Equal(expected, actual, new IssueComparer());
              
            }


            [Theory
            ,InlineData("EUR")]
            public void Company_Scores_Are_Computed_As_Specified(string region)
            {
              var result = CallComputation();
              Guid companyGuid = _dataFixture.GetCompaniesInRegion(region).FirstOrDefault();
              var expected = _dataFixture.ComputeCompanyScore(companyGuid);

                var companyScore =
                    result.CompanyRating.FirstOrDefault(x => x.CompanyId == companyGuid).Scores.ElementAt(0);
                                    
                  

                var actual=  new Tuple<Guid,  float>(companyGuid, companyScore.Score);
              
              _output.WriteLine(expected.Dump());

              Assert.Equal(expected, actual, new CompanyScoreComparer());
              
            }
        }

        private bool _disposed;

        protected override void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {
                    _sutClient.Dispose();
                }

                // release any unmanaged objects
                // set object references to null

                _disposed = true;
            }

            base.Dispose(disposing);
        }

        ~GovernanceScoreTests()
        {
            Dispose(false);
        }
    }

    
}